﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using SSPComm.SSPTransport;
using SSPComm.SSPCryption;
using SSPComm.SSPCommands;
using SSPComm.SSPReplies;

namespace SSPComm
{
    public class SSPController
    {
        private readonly Int32 SMART_HOPPER_RESTART_TMO = 3000;
        private readonly Int32 EMPTY_POLL_REPLY_FAILS_COUNTER = 30;
        private readonly SSPTransportManager _transport;
        private readonly UInt32 _serial;

        public SSPController(String inPortName, Byte inCommId, UInt32 inSerial, ReceivedEvent inDelegate, ThrowedException inException)
        {
            _transport = new SSPTransportManager(inPortName, inCommId, inDelegate, inException);
            _serial = inSerial;
        }

        public Boolean IsStarted
        {
            get
            {
                return _transport.IsStarted;
            }
        }

        public void Start()
        {
            _transport.Start();

            Init();
        }

        private void Init()
        {
            //5. Set Host Version 0x06
            SSPData res;
            res = _transport.SendData(new SSPSetProtocolData());
            if (!res.IsOK)
            {
                throw new Exception("SSP slave answer invalid: protocol not supported");
            }

            res = _transport.SendData(new SSPSetCalibrationData(SSPCalibrationMode.Auto));
            if (!res.IsOK)
            {
                throw new Exception("SSP slave answer invalid: auto calibration fails");
            }

            res = _transport.SendData(new SSPSetOptionsData(false));
            if (!res.IsOK)
            {
                throw new Exception("SSP slave answer invalid: set low speed fails");
            }

            //6. Enable
            res = _transport.SendData(new SSPEnableData());
            if (!res.IsOK)
            {
                throw new Exception("SSP slave answer invalid: not enabled");
            }

            _transport.StartPoll();
        }

        //private void DebugPrint(SSPData inData, Boolean inIsOut)
        //{
        //    Debug.Print(String.Format("{0} {1}\t{2}\t{3}\n", DateTime.Now.ToString("ss.fff"), this, inIsOut ? ">" : "<", inData));
        //}

		public SSPReply AddCoinAmount(UInt32 inCoinValue, UInt16 inCoinCount, String inCountryCode)
        {
            SSPSetCoinAmountData coinAmount = new SSPSetCoinAmountData(inCoinCount, new SSPCoin(inCoinValue, inCountryCode));
            SSPReply reply = SSPReplyParser.Parse(StopPollSendData(coinAmount));
            if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            {
                throw new SSPReplyException(coinAmount, reply, "Unexpected packet type");
            }
            return reply;
        }

        public SSPReply PayoutAmountQuick(UInt32 inAmount, String inCountryCode)
        {
            SSPPayoutAmountData payout = new SSPPayoutAmountData(inAmount, SSPPayoutFunc.Work, inCountryCode);
            SSPReply reply = SSPReplyParser.Parse(StopPollSendData(payout));
            if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            {
                throw new SSPReplyException(payout, reply, "Unexpected packet type");
            }
            return reply;
        }

        public SSPReply PayoutDenomQuick(UInt32 inCoinValue, UInt16 inCoinCount, String inCountryCode)
        {
            SSPCoinCount level = new SSPCoinCount(inCoinCount, inCoinValue, inCountryCode);
            SSPPayoutDenomData payout = new SSPPayoutDenomData(new List<SSPCoinCount> { level }, SSPPayoutFunc.Work);
            SSPReply reply = SSPReplyParser.Parse(StopPollSendData(payout));
            if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            {
                throw new SSPReplyException(payout, reply, "Unexpected packet type");
            }
            return reply;
        }

        [Obsolete("fails sometimes")]
        public SSPReply PayoutDenom(UInt32 inCoinValue, UInt16 inCoinCount, String inCountryCode)
        {
            _transport.StopPoll();
            SSPCoinsReply lastCoinsReply = null;
            SSPPayoutDenomData payout = null;
            try
            {
                SSPCoinCount level = new SSPCoinCount(inCoinCount, inCoinValue, inCountryCode);
                payout = new SSPPayoutDenomData(new List<SSPCoinCount> { level }, SSPPayoutFunc.Work);

                SSPData dataReceived = SendData(payout);
                SSPPayoutReply replyPayout = SSPReplyParser.ParsePayout(dataReceived);
                
                if (replyPayout.Type != SSPDataTypes.OK)
                {
                    String msg = String.Format("Payout fails: {0}", replyPayout.Type);
                    if (replyPayout.Type == SSPDataTypes.CmdErr)
                    {
                        msg = String.Format("{0}; {1}", msg, replyPayout.Error);
                    }
                    throw new SSPReplyException(payout, replyPayout, msg);
                }

                SSPData poll = new SSPPollData();
                SSPReply reply = null;
                SSPSmartHopperPollEvents ev = SSPSmartHopperPollEvents.Dispensing;
                Int32 stopCounter = 0;
                while (ev == SSPSmartHopperPollEvents.Dispensing)
                {
                    System.Threading.Thread.Sleep(SSPTransportManager.SSP_POLL_TMO);
                    dataReceived = SendData(poll);

                    reply = SSPReplyParser.Parse(dataReceived);

                    if ((reply as SSPSimpleReply) != null)
                    {
                        if (reply.Type == SSPDataTypes.OK)
                        {
                            if (stopCounter++ >= EMPTY_POLL_REPLY_FAILS_COUNTER)
                            {
                                throw new SSPReplyException(poll, reply, "SmartHopper has ignored command");
                            }
                            continue;
                        }
                        else
                        {
                            throw new SSPReplyException(poll, reply, "Unexpected packet type"); 
                        }
                    }

                    if (reply.Type == SSPDataTypes.CmdErr)
                    {
                        throw new SSPReplyException(poll, reply, "Command can not be processed");
                    }

                    if ((reply as SSPPayoutReply) != null)
                    {
                        //(reply as SSPPollReply).Event == SSPSmartHopperPollEvents.OK
                        throw new SSPReplyException(poll, reply, "Error while routing");
                    }

                    if ((reply as SSPCoinsReply) == null)
                    {
                        throw new SSPReplyException(poll, reply, "Unexpected packet type");
                    }
                    lastCoinsReply = reply as SSPCoinsReply;
                    ev = lastCoinsReply.Operations[0].Event;
                    if (lastCoinsReply.GetDispenced() != null)
                    {
                        return reply;
                    }
                }
                return reply;
            }
            catch (Exception exc)
            {
                throw new SSPCoinsReplyException(payout, lastCoinsReply, "Exception was thrown on payout operation.", exc);
            }
            finally
            {
                _transport.StartPoll();
            }
        }

        public Dictionary<SSPCoin, UInt16> GetCoinAmount()
        {
            SSPSetupReply setup = GetSetup() as SSPSetupReply;
            if (setup == null)
            {
                throw new Exception("Get setup fails");
            }
            Dictionary<SSPCoin, UInt16> result = new Dictionary<SSPCoin, UInt16>(setup.Coins.Length);
            SSPData answ;
            SSPAmountReply reply = null;
            SSPCoin currCoin;
            for (int i = 0; i < setup.Coins.Length; i++)
            {
                currCoin = new SSPCoin(setup.Coins[i].Value, setup.Coins[i].CountryCode);
                answ = StopPollSendData(new SSPGetCoinAmountData(currCoin));
                reply = SSPReplyParser.ParseAmount(answ);
                result.Add(currCoin, reply.Amount);
            }
            return result;
        }

        public UInt16 GetCoinAmount(UInt32 inCoinValue, String inCountryCode)
        {
            SSPData answ = StopPollSendData(new SSPGetCoinAmountData(new SSPCoin(inCoinValue, inCountryCode)));
            SSPAmountReply reply = SSPReplyParser.ParseAmount(answ);
            
            return reply.Amount;
        }

        public SSPReply GetSetup()
        {
            SSPGetSetupData setup = new SSPGetSetupData();

            SSPData received = StopPollSendData(setup);
            //if (!received.IsOK)
            //{
            //    throw new Exception("Unexpected packet type");
            //}
            return SSPReplyParser.ParseSetup(received);
        }

        public SSPReply SetOptions(Boolean inLowSpeed)
        {
            SSPSetOptionsData options = new SSPSetOptionsData(inLowSpeed);

            SSPReply reply = SSPReplyParser.Parse(StopPollSendData(options));
            if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            {
                throw new SSPReplyException(options, reply, "Unexpected packet type");
            }
            return reply;
        }

        public SSPReply SetRouting(UInt32 inCoinValue, SSPRoute inRoute, String inCountryCode)
        {
            SSPSetRoutingData routing = new SSPSetRoutingData(inRoute, new SSPCoin(inCoinValue, inCountryCode));

            SSPReply reply = SSPReplyParser.Parse(StopPollSendData(routing));
            if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            {
                throw new SSPReplyException(routing, reply, "Unexpected packet type");
            }
            return reply;
        }

        public UInt32 GetSerial()
        {
            SSPGetSerialData getSerial = new SSPGetSerialData();
            SSPReply reply = SSPReplyParser.ParseSerial(StopPollSendData(getSerial));
            return (reply as SSPGetSerialReply).Serial;
            //if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            //{
            //    throw new SSPReplyException(payout, reply, "Unexpected packet type");
            //}
            //return reply;

        }

        public Boolean IsStateOK(out SSPReply outReply, out String outDescription)
        {
            SSPData poll = new SSPPollData();
            outReply = SSPReplyParser.Parse(StopPollSendData(poll));

            if ((outReply.RawData.Length == 1) && (outReply.Type == SSPDataTypes.OK))
            {
                outDescription = "Ok";
                return true;
            }

            if (outReply is SSPPollReply)
            {
                var ev = (outReply as SSPPollReply).Event;
                switch (ev)
                {
                    case SSPSmartHopperPollEvents.LidOpen:
                        outDescription = "Lid is opened.";
                        break;
                    case SSPSmartHopperPollEvents.LidClosed:
                        outDescription = "Lid is closed.";
                        break;
                    case SSPSmartHopperPollEvents.CalibrationFail:
                        outDescription = "Calibration error.";
                        break;
                    case SSPSmartHopperPollEvents.CoinMechJammed:
                    case SSPSmartHopperPollEvents.Jammed:
                        outDescription = "Jammed.";
                        break;
                    case SSPSmartHopperPollEvents.FraudAttempt:
                        outDescription = "Fraud attempt.";
                        break;
                    case SSPSmartHopperPollEvents.Disabled:
                        outDescription = "Hopper is disabled.";
                        break;
                    default:
                        outDescription = String.Format("Unexpected error: {0}.", outReply);
                        break;
                }
            }
            else
            {
                outDescription = String.Format("Unexpected error: {0}.", outReply);
            }
            return false;
        }

        public SSPReply EmptyAll()
        {
            _transport.StopPoll();
            try
            {
                SSPEmptyAllData empty = new SSPEmptyAllData();
                SSPReply reply = SSPReplyParser.Parse(SendData(empty));
                if ((reply.RawData.Length != 1) || (reply.Type != SSPDataTypes.OK))
                {
                    throw new SSPReplyException(empty, reply, "Empty all fails");
                }

                SSPData poll = new SSPPollData();
                SSPSmartHopperPollEvents ev = SSPSmartHopperPollEvents.Emptying;
                Int32 stopCounter= 0;
                while (ev == SSPSmartHopperPollEvents.Emptying)
                {
                    System.Threading.Thread.Sleep(SSPTransportManager.SSP_POLL_TMO);
                    reply = SSPReplyParser.Parse(SendData(poll));
                    if (reply is SSPSimpleReply)
                    {
                        if (reply.Type == SSPDataTypes.OK)
                        {
                            stopCounter++;
                            if (stopCounter == EMPTY_POLL_REPLY_FAILS_COUNTER)
                            {
                                throw new SSPReplyException(poll, reply, "SmartHopper has ignored command");
                            }
                            continue;
                        }
                        else
                        {
                            throw new SSPReplyException(poll, reply, "Unexpected packet type");
                        }
                    }

                    if (reply as SSPPollReply == null)
                    {
                        throw new SSPReplyException(poll, reply, "Unexpected poll reply");
                    }
                    ev = (reply as SSPPollReply).Event;
                }
                return reply;
            }
            finally
            {
                _transport.StartPoll();
            }
        }

        public SSPReply SmartEmptyQuick()
        {
            SSPSmartEmptyData smartEmpty = new SSPSmartEmptyData();

            SSPReply reply = SSPReplyParser.Parse(StopPollSendData(smartEmpty));
            if (!((reply is SSPSimpleReply) && (reply.Type == SSPDataTypes.OK)))
            {
                throw new SSPReplyException(smartEmpty, reply, "Unexpected packet type");
            }
            return reply;
        }

        public SSPReply SmartEmpty()
        {
            _transport.StopPoll();
            SSPCoinsReply lastCoinsReply = null;
            SSPSmartEmptyData empty = null;
            try
            {
                empty = new SSPSmartEmptyData();
                SSPReply reply = SSPReplyParser.Parse(SendData(empty));
                if ((reply.RawData.Length != 1) || (reply.Type != SSPDataTypes.OK))
                {
                    throw new SSPReplyException(empty, reply, "Smart empty fails");
                }

                SSPData poll = new SSPPollData();
                SSPSmartHopperPollEvents ev = SSPSmartHopperPollEvents.SmartEmptying;
                Int32 stopCounter = 0;
                while (ev == SSPSmartHopperPollEvents.SmartEmptying)
                {
                    System.Threading.Thread.Sleep(SSPTransportManager.SSP_POLL_TMO);
                    reply = SSPReplyParser.Parse(SendData(poll));
                    if (reply is SSPSimpleReply)
                    {
                        if (reply.Type == SSPDataTypes.OK)
                        {
                            if (stopCounter++ >= EMPTY_POLL_REPLY_FAILS_COUNTER)
                            {
                                throw new SSPReplyException(poll, reply, "SmartHopper has ignored command");
                            }
                            continue;
                        }
                        else
                        {
                            throw new SSPReplyException(poll, reply, "Unexpected packet type");
                        }
                    }

                    if (reply as SSPCoinsReply == null)
                    {
                        throw new SSPReplyException(poll, reply, "Unexpected poll reply");
                    }
                    lastCoinsReply = reply as SSPCoinsReply;
                    ev = lastCoinsReply.Operations[0].Event;
                }
                return reply;
            }
            catch (Exception exc)
            {
                throw new SSPCoinsReplyException(empty, lastCoinsReply, "Exception was thrown on smart empty operation.", exc);
            }
            finally
            {
                _transport.StartPoll();
            }
        }

        public Dictionary<SSPCoin, SSPRoute> GetRouting()
        {
            SSPSetupReply setup = SSPReplyParser.ParseSetup(StopPollSendData(new SSPGetSetupData()));
            Dictionary<SSPCoin, SSPRoute> result = new Dictionary<SSPCoin, SSPRoute>(setup.Coins.Length);

            SSPCoin currCoin;
            SSPGetRoutingData routing;
            //SSPData currResult;
            SSPSetRoutingReply reply;
            for (int i = 0; i < setup.Coins.Length; i++)
            {
                currCoin = new SSPCoin(setup.Coins[i].Value, setup.Coins[i].CountryCode);
                routing = new SSPGetRoutingData(currCoin);
                reply = SSPReplyParser.ParseRouting(StopPollSendData(routing));
                result.Add(currCoin, reply.Route); 
            }
            return result;
        }

        public Boolean Reset()
        {
			_transport.StopPoll();
			Boolean result = false;
            try
            {
                SSPResetData reset = new SSPResetData();

                _transport.SendData(reset.Data);

                System.Threading.Thread.Sleep(SMART_HOPPER_RESTART_TMO);

                _transport.Reset();
                Init();
                result = true;
            }
            catch (Exception exc)
            {
                Console.WriteLine("err: {0}", exc.Message);
            }
            return result;
        }

        public void Stop()
        {
            _transport.StopPoll();
            _transport.Stop();
        }

        public SSPData StopPollSendData(SSPData inData)
        {
            SSPData dataReceived;
            _transport.StopPoll();
            try
            {
                dataReceived = _transport.SendData(inData);
            }
            catch (Exception exc)
            {
                throw exc;
            }
            finally
            {
                _transport.StartPoll();
            }
            return dataReceived; 
        }

        public SSPData SendData(SSPData inData)
        {
            return _transport.SendData(inData);
        }

        public void ValidateSerial()
        {
            UInt32 realSerial = GetSerial();
            if (realSerial != _serial)
            {
                throw new Exception("Serial invalid");
            }
        }
    }
}
